using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace PathX.Collection
{
    /// <summary>
    /// Ultra basic implementation of a dynamic array that forgoes most safety checks and relies on a certain usage pattern.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DynamicArray<T> :
        IDynamicArray<T>,
        IIterable<T>,
        IEnumerable<T>,
        IEnumerable,
        IIndexable<T>,
        ISortable<T>
    {
        private static readonly T[] _empty = new T[0];
        private T[] _items;
        private int _capacity;
        private int _used;

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        public DynamicArray() => this._items = DynamicArray<T>._empty;

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="capacity">The capacity.</param>
        public DynamicArray(int capacity)
        {
            this._items = new T[capacity];
            this._capacity = capacity;
        }

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="source">The source array.</param>
        public DynamicArray(T[] source)
        {
            this._used = this._capacity = source.Length;
            this._items = new T[this._capacity];
            Array.Copy((Array)source, (Array)this._items, this._capacity);
        }

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="source">The source list.</param>
        public DynamicArray(IIndexable<T> source)
        {
            this._used = this._capacity = source.count;
            this._items = new T[this._capacity];
            for (int idx = 0; idx < this._capacity; ++idx)
                this._items[idx] = source[idx];
        }

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="source">The source list.</param>
        public DynamicArray(IEnumerable<T> source)
        {
            this._items = source.ToArray<T>();
            this._used = this._capacity = this._items.Length;
        }

        /// <summary>Gets the count.</summary>
        /// <value>The count.</value>
        public int count => this._used;

        /// <summary>
        /// Gets the value with the specified index. There is no bounds checking on get.
        /// </summary>
        /// <param name="idx">The index.</param>
        /// <returns>The value at the index</returns>
        public T this[int idx] => this._items[idx];

        /// <summary>Adds the specified item.</summary>
        /// <param name="item">The item.</param>
        public void Add(T item)
        {
            if (this._used == this._capacity)
                this.Resize(Math.Max(this._capacity, 1) * 2);
            this._items[this._used++] = item;
        }

        /// <summary>Adds the range of items.</summary>
        /// <param name="items">The items.</param>
        public void AddRange(IIndexable<T> items)
        {
            int newCapacity = items.count + this._used;
            if (this._capacity < newCapacity)
                this.Resize(newCapacity);
            for (int idx = 0; idx < items.count; ++idx)
                this._items[this._used++] = items[idx];
        }

        /// <summary>Adds the range of items.</summary>
        /// <param name="items">The items.</param>
        public void AddRange(IEnumerable<T> items)
        {
            int newCapacity = items.Count<T>() + this._used;
            if (this._capacity < newCapacity)
                this.Resize(newCapacity);
            foreach (T obj in items)
                this._items[this._used++] = obj;
        }

        /// <summary>Removes the specified item.</summary>
        /// <param name="item">The item.</param>
        /// <returns>
        ///   <c>true</c> if found and removed; otherwise <c>false</c>
        /// </returns>
        public bool Remove(T item)
        {
            for (int index = 0; index < this._used; ++index)
            {
                if ((object)this._items[index] != null && this._items[index].Equals((object)item))
                {
                    this.RemoveAt(index);
                    return true;
                }
            }
            return false;
        }

        /// <summary>Removes the item at the specified index.</summary>
        /// <param name="index">The index.</param>
        public void RemoveAt(int index)
        {
            for (int index1 = index; index1 < this._used - 1; ++index1)
                this._items[index1] = this._items[index1 + 1];
            this._items[this._used - 1] = default(T);
            --this._used;
        }

        /// <summary>
        /// Reorders the array such that an item is moved form one index to another and the rest of the array adapts to that.
        /// </summary>
        /// <param name="fromIdx">The from (source) index.</param>
        /// <param name="toIdx">The to (destination) index.</param>
        public void Reorder(int fromIdx, int toIdx)
        {
            T obj = this._items[fromIdx];
            if (fromIdx < toIdx)
            {
                for (int index = fromIdx + 1; index <= toIdx; ++index)
                    this._items[index - 1] = this._items[index];
            }
            else
            {
                for (int index = fromIdx - 1; index >= toIdx; --index)
                    this._items[index + 1] = this._items[index];
            }
            this._items[toIdx] = obj;
        }

        /// <summary>Clears the array.</summary>
        public void Clear()
        {
            Array.Clear((Array)this._items, 0, this._used);
            this._used = 0;
        }

        /// <summary>Sorts this instance.</summary>
        public void Sort() => Array.Sort<T>(this._items);

        /// <summary>Sorts this instance using the specified comparer.</summary>
        /// <param name="comparer">The comparer.</param>
        public void Sort(IComparer<T> comparer) => Array.Sort<T>(this._items, 0, this._used, comparer);

        /// <summary>
        /// Sorts a subset of this instance using the default comparer of its members.
        /// </summary>
        /// <param name="index">The start index.</param>
        /// <param name="length">The length.</param>
        public void Sort(int index, int length) => Array.Sort<T>(this._items, index, length);

        /// <summary>
        /// Sorts a subset of this instance using the specified comparer.
        /// </summary>
        /// <param name="index">The start index.</param>
        /// <param name="length">The length.</param>
        /// <param name="comparer">The comparer.</param>
        public void Sort(int index, int length, IComparer<T> comparer)
        {
            if (index + length >= this._used)
                length = this._used - index;
            Array.Sort<T>(this._items, index, length, comparer);
        }

        /// <summary>
        /// Ensures a certain capacity of the array, i.e. resizes the array to hold the specified number of items if not already able to.
        /// </summary>
        /// <param name="capacity">The capacity to ensure.</param>
        public void EnsureCapacity(int capacity)
        {
            if (this._capacity >= capacity)
                return;
            this.Resize(capacity);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String" /> that represents this instance.
        /// </returns>
        public override string ToString() => "DynamicArray, count: " + (object)this.count;

        private void Resize(int newCapacity)
        {
            this._capacity = newCapacity;
            T[] destinationArray = new T[this._capacity];
            Array.Copy((Array)this._items, 0, (Array)destinationArray, 0, this._items.Length);
            this._items = destinationArray;
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            int count = this.count;
            for (int i = 0; i < count; ++i)
                yield return this._items[i];
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            int count = this.count;
            for (int i = 0; i < count; ++i)
                yield return (object)this._items[i];
        }
    }
}